﻿using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Behaviours.Collections;

namespace Behaviours.Entities
{
  public sealed class EntityManager : IDisposable
  {
    public static EntityManager Instance = new EntityManager();
    private readonly MyDictionary<int, Stack<Object>> objectPool = new MyDictionary<int, Stack<Object>>();
    private readonly ArrayPool<Entity> entities = new ArrayPool<Entity>();
    private bool isDispose;

    internal void AddEntity(Entity entity) => this.entities.Add(entity);

    internal void RemoveEntity(Entity entity) => this.entities.Remove(entity);

    public T Take<T>() where T : Object, new()
    {
      int hashCode = typeof (T).GetHashCode();
      Stack<Object> gobjectStack1;
      if (this.objectPool.TryGetValue(hashCode, out gobjectStack1))
      {
        if (gobjectStack1.Count > 0)
          return (T) gobjectStack1.Pop();
      }
      else
      {
        Stack<Object> gobjectStack2 = new Stack<Object>();
        this.objectPool.Add(hashCode, gobjectStack2);
      }
      return new T();
    }

    public void Push(Object @object)
    {
      int hashCode = @object.hashCode;
      Stack<Object> gobjectStack1;
      if (this.objectPool.TryGetValue(hashCode, out gobjectStack1))
      {
        gobjectStack1.Push(@object);
      }
      else
      {
        Stack<Object> gobjectStack2 = new Stack<Object>();
        this.objectPool.Add(hashCode, gobjectStack2);
        gobjectStack2.Push(@object);
      }
    }

    public void Push<T>(T[] array) where T : Object
    {
      int hashCode = typeof (T).GetHashCode();
      Stack<Object> gobjectStack1;
      if (this.objectPool.TryGetValue(hashCode, out gobjectStack1))
      {
        gobjectStack1.CopyTo((Object[]) array, 0);
      }
      else
      {
        Stack<Object> gobjectStack2 = new Stack<Object>((IEnumerable<Object>) array);
        this.objectPool.Add(hashCode, gobjectStack2);
      }
    }

    public Entity Create() 
    {
      Entity obj = this.Take<Entity>();
      this.AddEntity((Entity) obj);
      obj.manager = this;
      obj.Awake();
      return obj;
    }

    public void Run(bool worker = false)
    {
      int count = this.entities.Count;
      if (!worker)
      {
        for (int index = 0; index < count; ++index)
        {
          if (this.entities.buckets[index] && this.entities[index].isActive)
            this.entities[index].Execute();
        }
      }
      else
        Parallel.For(0, count, (Action<int>) (i =>
        {
          if (!this.entities.buckets[i] || !this.entities[i].isActive)
            return;
          this.entities[i].Execute();
        }));
    }

    public void Dispose()
    {
      if (this.isDispose)
        return;
      this.isDispose = true;
    }
  }
}
